import {
  BufferGeometry,
  Vector3,
  Vector2,
  Float32BufferAttribute,

} from "./three.js/build/three.module.js";

// 胶囊体
class CapsuleGeometry extends BufferGeometry {

  constructor( radius=1, height=1, radialSegments=8, heightSegments=1 ) {
    super();

    this.type="CapsuleGeometry";
    
		this.parameters = {
			radius: radius,
			height: height,
			radialSegments: radialSegments,
			heightSegments: heightSegments
		};

    radialSegments = Math.floor( radialSegments );
		heightSegments = Math.floor( heightSegments );

    let index = 0;
		const grid = [];

		const vertex = new Vector3();
		const normal = new Vector3();

		// buffers

		const indices = [];
		const vertices = [];
		const normals = [];
		const uvs = [];

    const halfHeight = height / 2;
    const allHeightSegments = radialSegments * 2 + heightSegments;
    const widthSegments = radialSegments;
    const phiStart = 0;
    const phiLength = Math.PI * 2;
    const thetaStart = 0;
    const thetaLength = Math.PI / 2;
    const thetaEnd = Math.PI;

		// generate vertices, normals and uvs

		for ( let iy = 0; iy <= allHeightSegments; iy ++ ) {

			const verticesRow = [];

			const v = iy  / allHeightSegments;

			// special case for the poles

			let uOffset = 0;

			if ( iy == 0 && thetaStart == 0 ) {

				uOffset = 0.5 / widthSegments;

			} else if ( iy == allHeightSegments && thetaEnd == Math.PI ) {

				uOffset = - 0.5 / widthSegments;

			}

			for ( let ix = 0; ix <= widthSegments; ix ++ ) {

				const u = ix / widthSegments;

        let iv = v;
				// vertex
        if( iy <= radialSegments ){ // top
          iv = iy / radialSegments;
          vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + iv * thetaLength );
          vertex.y = radius * Math.cos( thetaStart + iv * thetaLength ) + halfHeight;
          vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + iv * thetaLength );
        }
        else if( iy > radialSegments && iy < radialSegments + heightSegments ){ // middle
          vertex.x = - radius * Math.cos( phiStart + u * phiLength );
          vertex.y = - halfHeight + (iy - radialSegments) / heightSegments * height;
          vertex.z = radius * Math.sin( phiStart + u * phiLength );
        }
        else {  // bottom
          iv = (iy - heightSegments) / radialSegments;
          vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + iv * thetaLength );
          vertex.y = radius * Math.cos( thetaStart + iv * thetaLength ) - halfHeight;
          vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + iv * thetaLength );
        }

				vertices.push( vertex.x, vertex.y, vertex.z );

				// normal

				normal.copy( vertex ).normalize();
				normals.push( normal.x, normal.y, normal.z );

				// uv

				uvs.push( u + uOffset, 1 - v );

				verticesRow.push( index ++ );

			}

			grid.push( verticesRow );

		}

		// indices

		for ( let iy = 0; iy < allHeightSegments; iy ++ ) {

			for ( let ix = 0; ix < widthSegments; ix ++ ) {

				const a = grid[ iy ][ ix + 1 ];
				const b = grid[ iy ][ ix ];
				const c = grid[ iy + 1 ][ ix ];
				const d = grid[ iy + 1 ][ ix + 1 ];

				if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
				if ( iy !== allHeightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );

			}

		}

		// build geometry

		this.setIndex( indices );
		this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
		this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
		this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );

  }

	static fromJSON( data ) {

		return new CapsuleGeometry( data.radius, data.height, data.radialSegments, data.heightSegments );

	}
}

// 扩展圆柱体-传送带
class ExCylinderGeometry extends BufferGeometry {

	// radius 半径/高度
	// width 宽/长度
	// height 高度
	constructor( radius=1, height = 1, width=1, radialSegments=8, heightSegments=2, widthSegments=1 ) {
    super();

    this.type="ExCylinderGeometry";
    
		this.parameters = {
			radius: radius,
			height: height,
			width: width,
			radialSegments: radialSegments,
			heightSegments: heightSegments,
			widthSegments:widthSegments,
		};

		const scope = this;

    radialSegments = Math.floor( radialSegments );
		heightSegments = Math.floor( heightSegments );
		widthSegments = Math.floor( widthSegments );

    // buffers

		const indices = [];
		const vertices = [];
		const normals = [];
		const uvs = [];

		// helper variables

		let index = 0;
		const indexArray = [];
		const halfHeight = height / 2;
		const halfWidth = width / 2;
		let groupStart = 0;

    const thetaLength = Math.PI;
		
		const allWidthSegments = ( radialSegments + widthSegments ) * 2 ;

		const uLength = radius * 2 + width;
		const uRadius = radius / uLength;
		const uWidth = width / uLength;
		// const uOffset = uWidth * 0.5;

		generateTorso();
		generateCap(true);
		generateCap(false);
		
		// build geometry

		this.setIndex( indices );
		this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
		this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
		this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );

		function generateTorso() {

			const normal = new Vector3();
			const vertex = new Vector3();

			let groupCount = 0;
			// generate vertices, normals and uvs

			for ( let y = 0; y <= heightSegments; y ++ ) {

				const indexRow = [];

				const v = y / heightSegments;

				//
				
				for ( let x = 0; x <= allWidthSegments; x ++ ) {

					const u = x / allWidthSegments;

					if( x <= radialSegments ) {

						const theta = ( x / radialSegments ) * thetaLength;

						const sinTheta = Math.sin( theta );
						const cosTheta = Math.cos( theta );

						// vertex

						vertex.x = radius * sinTheta + halfWidth;
						vertex.y = - v * height + halfHeight;
						vertex.z = radius * cosTheta;
						vertices.push( vertex.x, vertex.y, vertex.z );

						// normal

						normal.set( sinTheta, 0, cosTheta ).normalize();
						normals.push( normal.x, normal.y, normal.z );

					}
					else if ( x > radialSegments && x < radialSegments + widthSegments ) {
						// vertex

						vertex.x = -( ( x - radialSegments ) / widthSegments ) * width + halfWidth;
						vertex.y = - v * height + halfHeight;
						vertex.z = -radius;
						vertices.push( vertex.x, vertex.y, vertex.z );

						// normal

						normal.set( 0, 0, -1 ).normalize();
						normals.push( normal.x, normal.y, normal.z );

					}
					else if( x >= (radialSegments + widthSegments) && x <= radialSegments * 2 + widthSegments ) {
						const theta = (x - widthSegments - radialSegments ) / radialSegments * thetaLength + Math.PI;

						const sinTheta = Math.sin( theta );
						const cosTheta = Math.cos( theta );

						// vertex

						vertex.x = radius * sinTheta - halfWidth;
						vertex.y = - v * height + halfHeight;
						vertex.z = radius * cosTheta;
						vertices.push( vertex.x, vertex.y, vertex.z );

						// normal

						normal.set( sinTheta, 0, cosTheta ).normalize();
						normals.push( normal.x, normal.y, normal.z );

					}
					else {

						// vertex

						vertex.x = ( ( x - radialSegments * 2 - widthSegments ) / widthSegments ) * width - halfWidth;
						vertex.y = - v * height + halfHeight;
						vertex.z = radius;
						vertices.push( vertex.x, vertex.y, vertex.z );

						// normal

						normal.set( 0, 0, 1 ).normalize();
						normals.push( normal.x, normal.y, normal.z );

					}

					// uv

					uvs.push( u, 1 - v );

					// save index of vertex in respective row

					indexRow.push( index ++ );

				}

				// now save vertices of the row in our index array

				indexArray.push( indexRow );

			}

			// generate indices

			for ( let x = 0; x < allWidthSegments; x ++ ) {

				for ( let y = 0; y < heightSegments; y ++ ) {

					// we use the index array to access the correct indices

					const a = indexArray[ y ][ x ];
					const b = indexArray[ y + 1 ][ x ];
					const c = indexArray[ y + 1 ][ x + 1 ];
					const d = indexArray[ y ][ x + 1 ];

					// faces

					indices.push( a, b, d );
					indices.push( b, c, d );

					// update group counter

					groupCount += 6;

				}

			}

			// add a group to the geometry. this will ensure multi material support

			scope.addGroup( groupStart, groupCount, 0 );

			// calculate new start value for groups

			groupStart += groupCount;			

		}

		function generateCap( top ) {

			// save the index of the first center vertex
			const centerIndexStart = index;

			const uv = new Vector2();
			const vertex = new Vector3();

			let groupCount = 0;

			const sign = ( top === true ) ? 1 : - 1;

			// first we generate the center vertex data of the cap.
			// because the geometry needs one set of uvs per face,
			// we must generate a center vertex per face/segment

			for ( let x = 0; x <= allWidthSegments; x ++ ) {

				// vertex

				vertices.push( 0, halfHeight * sign, 0 );

				// normal

				normals.push( 0, sign, 0 );

				// uv

				uvs.push( 0.5, 0.5 );

				// increase index

				index ++;

			}

			// save the index of the last center vertex
			const centerIndexEnd = index;

			// now we generate the surrounding vertices, normals and uvs

			for ( let x = 0; x <= allWidthSegments; x ++ ) {

				const u = x / radialSegments;

				if( x <= radialSegments ) {
					const theta = ( x / radialSegments ) * thetaLength;

					const cosTheta = Math.cos( theta );
					const sinTheta = Math.sin( theta );

					// vertex

					vertex.x = radius * sinTheta + halfWidth;
					vertex.y = halfHeight * sign;
					vertex.z = radius * cosTheta;
					vertices.push( vertex.x, vertex.y, vertex.z );

					// uv

					uv.x = ( cosTheta * 0.5 ) * uRadius - uWidth / 2;
					uv.y = ( sinTheta * 0.5 * sign ) + 0.5;
					uvs.push( uv.x, uv.y );
				}

				else if( x > radialSegments && x < radialSegments + widthSegments ) {
					
					vertex.x = -((x - radialSegments) / widthSegments) * width + halfWidth;
					vertex.y = halfHeight * sign;
					vertex.z = -radius;
					vertices.push( vertex.x, vertex.y, vertex.z );

					uv.x =  ( ( (x - radialSegments) / widthSegments ) - 0.5 ) * uWidth;
					uv.y = 0.25 + 0.25 * sign;
					uvs.push( uv.x, uv.y );

				}

				else if ( x >= radialSegments + widthSegments && x <= radialSegments * 2 + widthSegments ) {

					const theta = (x - widthSegments - radialSegments ) / radialSegments * thetaLength + Math.PI;

					const cosTheta = Math.cos( theta );
					const sinTheta = Math.sin( theta );

					// vertex

					vertex.x = radius * sinTheta - halfWidth;
					vertex.y = halfHeight * sign;
					vertex.z = radius * cosTheta;
					vertices.push( vertex.x, vertex.y, vertex.z );

					// uv

					uv.x = ( cosTheta * 0.5 ) * uRadius + uWidth / 2;
					uv.y = ( sinTheta * 0.5 * sign ) + 0.5;
					uvs.push( uv.x, uv.y );

				}

				else {

					vertex.x = -((x - radialSegments * 2 - widthSegments) / widthSegments) * width + halfWidth;
					vertex.y = halfHeight * sign;
					vertex.z = radius;
					vertices.push( vertex.x, vertex.y, vertex.z );

					uv.x =  ( 0.5 - (x - radialSegments * 2 - widthSegments) / widthSegments ) * uWidth;
					uv.y = 0.5 + 0.5 * sign;
					uvs.push( uv.x, uv.y );

				}


				// normal

				normals.push( 0, sign, 0 );

				// increase index

				index ++;

			}

			vertex.set( 
				halfWidth, halfHeight * sign, radius
			);
			vertices.push( vertex.x, vertex.y, vertex.z );

			// normal

			normals.push( 0, sign, 0 );

			// increase index

			index ++;

			// generate indices

			for ( let x = 0; x <= allWidthSegments; x ++ ) {

				const c = centerIndexStart + x;
				const i = centerIndexEnd + x;

				if ( top === true ) {

					// face top

					indices.push( i, i + 1, c );

				} else {

					// face bottom

					indices.push( i + 1, i, c );

				}

				groupCount += 3;

			}

			// add a group to the geometry. this will ensure multi material support

			scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 );

			// calculate new start value for groups

			groupStart += groupCount;

		}

  }

	static fromJSON( data ) {

		return new ExCylinderGeometry( data.radius, data.height, data.width, data.radialSegments, data.heightSegments, data.widthSegments );

	}
	
}

export {
  CapsuleGeometry,
	ExCylinderGeometry,
};